package com.untour.seqid.config.leaf.segment;


import com.untour.im.exception.BusinessException;
import com.untour.seqid.config.exception.SeqIdExceptionCode;
import com.untour.seqid.config.leaf.IDGen;
import com.untour.seqid.config.leaf.LeafProperties;
import com.untour.seqid.config.leaf.segment.domain.LeafAllocDO;
import com.untour.seqid.config.leaf.segment.model.SegmentBuffer;
import com.untour.seqid.config.leaf.segment.schedule.UpdateThreadFactory;
import com.untour.seqid.config.leaf.segment.service.TagsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class SegmentIDGenImpl implements IDGen {

    private static Map<String, SegmentBuffer> bizTagsCache = new ConcurrentHashMap<>();

    private static final ExecutorService service = new ThreadPoolExecutor(5,
            Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
            new SynchronousQueue<>(), new UpdateThreadFactory());

    private final TagsService tagsService;

    private final LeafProperties leafProperties;

    public SegmentIDGenImpl(TagsService tagsService, LeafProperties leafProperties) {
        this.tagsService = tagsService;
        this.leafProperties = leafProperties;
    }

    @Override
    public void init() {
        log.info("启动服务初始化开始");
        //加载初始化配置
        this.initCacheFromDb();
        //定时同步db中新的数据到缓存
        this.scheduleUpdateCache();
        log.info("启动服务初始化结束");
    }


    /**
     * 初始化缓存
     */
    private void initCacheFromDb() {
        log.info("update cache from db");
        try {
            Set<String> biTagSet = tagsService.queryAllBizTags();
            this.addCache(biTagSet);
            this.removeCache(biTagSet);
        } catch (Exception e) {
            log.warn("update cache from db exception", e);
            throw e;
        }
    }

    /**
     * 增加缓存
     *
     * @param bizTagSet
     */
    private void addCache(Set<String> bizTagSet) {
        if (!CollectionUtils.isEmpty(bizTagSet)) {
            //将dbTags中新加的tag添加cache，通过遍历dbTags，判断是否在cache中存在，不存在就添加到cache
            bizTagSet.stream().forEach(bizTag -> {
                if (!bizTagsCache.containsKey(bizTag)) {
                    SegmentBuffer buffer = new SegmentBuffer();
                    buffer.setBizTag(bizTag);
                    bizTagsCache.put(bizTag, buffer);
                    log.info("Add tag {} from db to IdCache, SegmentBuffer {}", bizTag, buffer);
                }
            });
        }
    }

    /**
     * 删除缓存
     *
     * @param bizTagSet
     */
    private void removeCache(Set<String> bizTagSet) {
        if (!CollectionUtils.isEmpty(bizTagSet)) {
            //将cache中已失效的tag从cache删除，通过遍历cacheTags，判断是否在dbTagSet中存在，不存在说明过期，直接删除
            bizTagsCache.forEach((bizTag, segmentBuffer) -> {
                if (!bizTagsCache.containsKey(bizTag)) {
                    bizTagsCache.remove(bizTag);
                    log.info("Remove tag {} from IdCache", bizTag);
                }
            });
        } else {
            bizTagsCache.forEach((bizTag, segmentBuffer) -> {
                bizTagsCache.remove(bizTag);
                log.info("Remove tag {} from IdCache", bizTag);
            });
        }
    }

    /**
     * 定时更新缓存（增加新增的项目，剔除已删除的项目）
     * 此处有个问题，如果数据库中插入了新的记录A，
     * 定时任务没有及时更新缓存，此时获取A的编号，
     * 是无法获取的，缓存中不存在这个项目
     */
    private void scheduleUpdateCache() {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r);
            t.setName("sync-init-idCache-thread");
            t.setDaemon(true);
            return t;
        });
        service.scheduleWithFixedDelay(this::initCacheFromDb,
                leafProperties.getSegment().getInitialDelay(),
                leafProperties.getSegment().getDelay(),
                leafProperties.getSegment().getTimeUnit());
    }

    public long get(String bizTag) {
        return this.get(bizTag, 1);
    }

    @Override
    public long get(String bizTag, long incrValue) {
        SegmentBuffer segmentBuffer = bizTagsCache.get(bizTag);
        if (null == segmentBuffer) {
            log.error("bizTag不存在:{}", bizTag);
            throw new BusinessException(SeqIdExceptionCode.EXCEPTION_ID_KEY_NOT_EXISTS);
        }
        if (!segmentBuffer.isInitOk()) {
            log.warn("bizTag未初始化:{}", bizTag);
            synchronized (segmentBuffer) {
                if (!segmentBuffer.isInitOk()) {
                    log.info("bizTag开始初始化:{}", bizTag);
                    this.initSegmentFromDb(segmentBuffer);
                    log.info("bizTag结束初始化:{}", bizTag);
                }
            }
        }
        return this.getIdFromSegmentBuffer(segmentBuffer, incrValue);
    }

    /**
     * 初始化缓存
     *
     * @param segmentBuffer
     */
    private void initSegmentFromDb(SegmentBuffer segmentBuffer) {
        segmentBuffer.setSegmentInfo(this.getSegmentInfo(segmentBuffer.getBizTag()));
        //缓存初始化成功
        segmentBuffer.setInitOk(true);
    }

    /**
     * 获取号段
     *
     * @param bizTag
     * @return
     */
    private SegmentBuffer.SegmentInfo getSegmentInfo(String bizTag) {
        LeafAllocDO leafAlloc = tagsService.updateMaxIdAndGetLeafAlloc(bizTag);
        SegmentBuffer.SegmentInfo segmentInfo = new SegmentBuffer.SegmentInfo();
        segmentInfo.setStep(leafAlloc.getStep());
        segmentInfo.setMaxId(leafAlloc.getMaxId());
        segmentInfo.initValue();
        return segmentInfo;
    }

    /**
     * 获取号段
     *
     * @param bizTag
     * @param nextStep
     * @return
     */
    private SegmentBuffer.SegmentInfo getSegmentInfo(String bizTag, int nextStep) {
        LeafAllocDO leafAlloc = tagsService.updateMaxIdByCustomStepAndGetLeafAlloc(bizTag, nextStep);
        SegmentBuffer.SegmentInfo segmentInfo = new SegmentBuffer.SegmentInfo();
        segmentInfo.setStep(nextStep);
        segmentInfo.setMaxId(leafAlloc.getMaxId());
        segmentInfo.initValue();
        return segmentInfo;
    }

    /**
     * 获取号码
     *
     * @param segmentBuffer
     * @param incrValue
     * @return
     */
    private long getIdFromSegmentBuffer(SegmentBuffer segmentBuffer, long incrValue) {
        while (true) {
            //初始化二级缓存
            this.initNextSegment(segmentBuffer, incrValue);

            //切换缓存
            boolean swiched = this.swichSegmentInfo(segmentBuffer, incrValue);
            if(!swiched){
                this.sleepTime(segmentBuffer,incrValue);
                continue;
            }
            //lock
            segmentBuffer.getSegmentLock().readLock().lock();
            try {
                log.info("get id,[{}]", segmentBuffer.getSegmentInfo().getMaxId());
                long value = segmentBuffer.getSegmentInfo().getValue().getAndAdd(incrValue);
                if (value < segmentBuffer.getSegmentInfo().getMaxId()) {
                    return value;
                }
            } finally {
                //unlock
                segmentBuffer.getSegmentLock().readLock().unlock();
            }
        }
    }

    /**
     * 休眠-等待切换二级缓存
     * @param segmentBuffer
     * @param incrValue
     */
    private void sleepTime(SegmentBuffer segmentBuffer, long incrValue){
        int cycleNum = 0;
        while (!segmentBuffer.getSegmentInfo().needSwitchNext(incrValue)){
            try {
                log.info("sleep waite switch next segment:{}", segmentBuffer.getBizTag());
                Thread.sleep(leafProperties.getSegment().getSleepMillis());
            } catch (InterruptedException e) {
                log.error("sleep error", e);
            }
            cycleNum++;
            if(cycleNum >= leafProperties.getSegment().getCycles()){
                log.info("sleep too long,cycleNum:{}", cycleNum);
                return;
            }
        }

    }

    /**
     * 判断是否需要初始化二级缓存
     *
     * @param segmentBuffer
     * @param incrValue
     */
    private boolean initNextSegment(SegmentBuffer segmentBuffer, long incrValue) {
        if (!segmentBuffer.getSegmentInfo().needInitNext(incrValue)) {
            return false;
        }
        log.info("need init next cache,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
        //lock
        boolean tried = segmentBuffer.getInitNextSegmentLock().tryAcquire();
        if (tried) {
            log.info("init next cache get lock,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
            service.execute(() -> this.initNextCache(segmentBuffer));
        }
        return true;
    }

    /**
     * 初始化二级缓存
     *
     * @param segmentBuffer
     */
    private void initNextCache(SegmentBuffer segmentBuffer) {
        try {
            if (Objects.nonNull(segmentBuffer.getSegmentInfo().getNext())) {
                return;
            }
            log.info("init next cache,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
            if (segmentBuffer.getUpdateCacheTimestamp() == 0) {
                segmentBuffer.getSegmentInfo().setNext(this.getSegmentInfo(segmentBuffer.getBizTag()));
                segmentBuffer.setUpdateCacheTimestamp(System.currentTimeMillis());
            } else {
                long duration = System.currentTimeMillis() - segmentBuffer.getUpdateCacheTimestamp();
                int nextStep = this.getNextStep(segmentBuffer, duration);
                segmentBuffer.getSegmentInfo().setNext(this.getSegmentInfo(segmentBuffer.getBizTag(), nextStep));
                segmentBuffer.setUpdateCacheTimestamp(System.currentTimeMillis());
            }
        } finally {
            //unlock
            log.info("init next cache unlock,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
            segmentBuffer.getInitNextSegmentLock().release();
        }
    }

    /**
     * 计算步长
     *
     * @param segmentBuffer
     * @param duration
     * @return
     */
    private int getNextStep(SegmentBuffer segmentBuffer, long duration) {
        int nextStep = segmentBuffer.getSegmentInfo().getStep();
        if (duration < leafProperties.getSegment().getSegmentDuration()) {
            if (nextStep * 2 > leafProperties.getSegment().getMaxStep()) {
                //do nothing
            } else {
                nextStep = nextStep * 2;
            }
        } else if (duration < leafProperties.getSegment().getSegmentDuration() * 2) {
            //do nothing with nextStep
        } else {
            nextStep = nextStep * 9 / 10;
        }
        return nextStep;
    }

    /**
     * 切换缓存
     *
     * @param segmentBuffer
     * @param incrValue
     */
    private boolean swichSegmentInfo(SegmentBuffer segmentBuffer, long incrValue) {
        if (!segmentBuffer.getSegmentInfo().needSwitchNext(incrValue)) {
            log.info("no need switch cache,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
            return true;
        }
        log.info("need switch cache,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
        //lock
        boolean locked = segmentBuffer.getSegmentLock().writeLock().tryLock();
        if (!locked) {
            log.warn("switch cache get lock failed,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
            return false;
        }
        log.info("switch cache get lock,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
        try {
            if (!segmentBuffer.getSegmentInfo().needSwitchNext(incrValue)) {
                log.info("no need switch cache again,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
                return true;
            }
            AtomicLong nowValue = segmentBuffer.getSegmentInfo().getValue();
            SegmentBuffer.SegmentInfo nextSegmentInfo = segmentBuffer.getSegmentInfo().getNext();
            //设置当前号段初始值=上个号段的当前值
            nextSegmentInfo.getValue().set(nowValue.get());
            segmentBuffer.setSegmentInfo(nextSegmentInfo);
            log.info("switch cache,[{}:{}]", segmentBuffer.getBizTag(), segmentBuffer.getSegmentInfo().getMaxId());
        } finally {
            //unlock
            segmentBuffer.getSegmentLock().writeLock().unlock();
            log.info("switch cache unlock,[{}]", segmentBuffer.getSegmentInfo().getMaxId());
        }
        return true;
    }

}
